#pragma once
#ifndef TENSORDENSESTRING_H_
#define TENSORDENSESTRING_H_

namespace cmpMat
{
    namespace{
    // helper methods
    void cleanFrontBack(std::istream& in,
                    const std::string& frontChr,
                    const std::string& rearChr,
                    std::istream::streampos* start,
                    std::istream::streampos* end)
    {
        std::istream::streampos astrt = *start;
        in.seekg(*start);
        while ((bool)in &&
               (frontChr.find((char)in.get()) != std::string::npos))
        { }
        *start = in.tellg() - (std::istream::streampos)1;

        in.seekg(*end);

        in.unget();
        while ((bool)in &&
               (rearChr.find((char)in.get()) != std::string::npos))
        {
            in.unget().unget();
        }
        *end = in.tellg();

        in.seekg(astrt);
    }

    void findDelims(std::istream& in,
            std::istream::streampos start,
            std::istream::streampos end,
            const std::string& chr, const std::string& chldChar,
            std::vector<int>* chrsp, std::vector<int>* childChrsp)
    {
        std::vector<int>& chrs = *chrsp;
        std::vector<int>& childChrs = *childChrsp;
        std::vector<int> chrsTemp;
        std::vector<int> childChrsTemp;
        std::string stopSet = chr + chldChar;

        int bufferSize = 1024;
        char buffer[1024];
        in.seekg(start);
        int curPos = (int)start;
        chrsTemp.push_back(curPos - 1);
        while ((bool)in && in.tellg() < end)
        {
            in.read(buffer, std::min(bufferSize, (int)(end - in.tellg())));
            int readCnt = (int)in.gcount();
            char* index = buffer;
            while ((index = std::strpbrk(index, stopSet.c_str())) != nullptr)
            {
                int pos = curPos + (index - buffer);
                if (chr.find(*index) != chr.npos)
                    chrsTemp.push_back(pos);
                else
                    childChrsTemp.push_back(pos);
                index++;
            }
            curPos += readCnt;
        }
        chrsTemp.push_back(curPos);
        int chldStart = -1;
        for (int i = 0; i < (chrsTemp.size() - 1); i++)
        {
            int strt = chldStart + 1;
            for (int j = strt;
                 j < childChrsTemp.size();
                 j++)
            {
                if (childChrsTemp[j] > chrsTemp[i+1])
                    break;
                chldStart = j;
                if ((j >= (childChrsTemp.size() - 1) ||
                    childChrsTemp[j] != childChrsTemp[j+1] - 1) &&
                    childChrsTemp[j] != chrsTemp[i] + 1 &&
                    childChrsTemp[j] != chrsTemp[i+1] - 1)
                {
                    childChrs.push_back(childChrsTemp[j]);
                }
            }
            chrs.push_back(chrsTemp[i]);
        }
        chrs.push_back(chrsTemp.back());

    }

    std::string getString(std::istream& in,
        std::istream::streampos start,
        std::istream::streampos end)
    {
        std::string ret;
        std::istream::streampos astart = in.tellg();
        in.seekg(start);
        ret = std::string((int)end - (int)start, ' ');
        int i = -1;
        while (in.tellg() < end)
            ret[++i] = (char)in.get();
        in.seekg(astart);
        return ret;
    }

    template <typename _elementType>
    _elementType parseFromString(const std::string& str)
    {
        _elementType ret;
        std::stringstream elemStr(str);
        elemStr >> ret;
        if (!elemStr.eof())
            throw exception::Exception("Could not parse: " + str);
        return ret;
    }
    // end helper methods
    }

    template <typename _T>
    TensorDense<_T> TensorDense<_T>::loadFromMAT(std::istream& in)
    {
        TensorDense<_T> out;
        std::vector<int> chrs;
        std::vector<int> chrsChld;
        std::istream::streampos start = in.tellg();
        in.seekg(0, std::ios::end);
        std::istream::streampos end = in.tellg();
        in.seekg(start);
        cleanFrontBack(in, "[ ", "] ", &start, &end);
        findDelims(in, start, end, ";", " ,", &chrs, &chrsChld);

        int rows = chrs.size() - 1;
        int cols = (chrsChld.size() + rows) / rows;

        if (cols == 1)
            out.SetSize(rows);
        else
            out.SetSize(rows, cols);

        int firstCols = -1;
        if (chrs.size() > 0)
            chrs[0]++;
        for (int row = 0,chldIndex = 0; row < (chrs.size() - 1); row++)
        {
            int currentCol = 0;
            bool firstChld = true;
            bool breakWhile = false;
            while (!breakWhile)
            {
                if (firstChld)
                    start = chrs[row];
                else
                    start = chrsChld[chldIndex - 1];
                if (chldIndex >= chrsChld.size() ||
                    chrsChld[chldIndex] >= chrs[row + 1])
                {
                    end = chrs[row + 1];
                    breakWhile = true;
                }
                else
                {
                    end = chrsChld[chldIndex];
                    chldIndex++;
                }

                firstChld = false;
                cleanFrontBack(in, ", ;", ", ;", &start, &end);
                in.seekg(start);

                out(row, currentCol) = parseFromString<_T>(getString(in, start, end));
                currentCol++;
            }
            if (firstCols != currentCol)
                if (row == 0)
                    firstCols = currentCol;
                else
                    throw exception::Exception("rows are not equal size");
        }
        return out;
    }

    template <typename _T>
    TensorDense<_T> TensorDense<_T>::loadFromMAT(const std::string& in)
    {
         std::istringstream sstr(in);
         return loadFromMAT(sstr);
    }

    template <typename _T>
    std::string TensorDense<_T>::toStringFormat(const std::string& format) const
    {
        std::stringstream outStr;
        bool matStyle = false;
        if (format.find("mat") != std::string::npos)
            matStyle = true;

        std::string beginChar = "";
        std::string endChar = "";
        std::string rowEnd = "\n";
        std::string colEnd = " ";
        if (matStyle)
        {
            beginChar = "[";
            endChar = "]";
            rowEnd = ";";
            colEnd = ",";
        }

        if (_base::rank() == 2)
        {
            outStr << beginChar;
            for (int row = 0; row < _base::size0(); row++)
            {
                for (int col = 0; col < _base::size1(); col++)
                {
                    outStr << (*this)(row, col);
                    if (col != _base::size1() - 1)
                        outStr << colEnd;
                }
                if (row != _base::size0() - 1)
                    outStr << rowEnd;
            }
            outStr << endChar;
        }
        else
        {
            throw exception::Exception("print does not support this rank");
        }
        return outStr.str();
    }

}


#endif